package com.swak.devtools.apidoc.parse.parser;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.List;
import java.util.Map;

import org.springframework.core.annotation.AnnotatedElementUtils;

import com.swak.annotation.ApiDoc;
import com.swak.annotation.RequestMapping;
import com.swak.annotation.RequestMethod;
import com.swak.devtools.apidoc.parse.ApiClass;
import com.swak.devtools.apidoc.parse.ApiMethod;
import com.swak.devtools.apidoc.parse.ApiParam;
import com.swak.utils.Lists;
import com.swak.utils.StringUtils;
import com.swak.utils.router.RouterUtils;

public class MethodParser extends AbstractParser {

	private final Class<?> type;
	private final Method method;
	private final Map<TypeVariable<?>, Type> paramVariablesMappers;
	private final ApiMethod apiMethod;

	public MethodParser(Class<?> type, Method method, Map<TypeVariable<?>, Type> paramVariablesMappers) {
		this.type = type;
		this.method = method;
		this.paramVariablesMappers = paramVariablesMappers;
		this.apiMethod = new ApiMethod();
	}

	public ApiMethod parse() {

		// 基本信息
		this.parseInfo();

		// 处理 Api 信息
		this.parseApi();

		// 参数
		this.parseParams();

		// 返回值
		this.parseReturn();

		// return
		return this.apiMethod;
	}

	private void parseInfo() {

		// 方法Doc
		ApiDoc doc = AnnotatedElementUtils.findMergedAnnotation(method, ApiDoc.class);
		if (doc != null) {
			this.apiMethod.setApiName(doc.function());
			this.apiMethod.setApiModule(doc.module());
			this.apiMethod.setApiDescribe(doc.describe());
		}

		// 方法签名
		Class<?>[] parameterTypes = parseMethodParams(method, paramVariablesMappers);
		String methodParamDesc = EMPTY_PARAM;
		if (!(parameterTypes == null || parameterTypes.length == 0)) {
			StringBuilder builder = new StringBuilder();
			for (Class<?> clz : parameterTypes) {
				String className = buildClassName(clz);
				builder.append(className).append(PARAM_CLASS_SPLIT);
			}
			methodParamDesc = builder.substring(0, builder.length() - 1);
		}
		this.apiMethod.setName(method.getName());
		this.apiMethod.setSignature(type.getName() + "." + method.getName() + "(" + methodParamDesc + ")");
	}

	private void parseParams() {
		Parameter[] params = method.getParameters();
		String[] names = RouterUtils.getParameterNames(method);
		for (int i = 0; i < params.length; i++) {
			String name = names[i];
			Parameter param = params[i];
			this.parseParam(i, name, param, paramVariablesMappers);
		}
	}

	private void parseParam(int index, String name, Parameter param, Map<TypeVariable<?>, Type> paramVariablesMappers) {
		try {
			ParsedStat.start();
			ApiParam apiParam = ParamParser.parse(index, name, param, paramVariablesMappers);
			this.apiMethod.addParam(apiParam);
		} finally {
			ParsedStat.finish();
		}
	}

	private void parseApi() {
		RequestMapping classMapping = AnnotatedElementUtils.findMergedAnnotation(type, RequestMapping.class);
		RequestMapping methodMapping = AnnotatedElementUtils.findMergedAnnotation(method, RequestMapping.class);
		if (classMapping != null && methodMapping != null) {
			String[] patterns1 = classMapping.value();
			String[] patterns2 = methodMapping.value();
			List<String> result = Lists.newArrayList();
			if (patterns1.length != 0 && patterns2.length != 0) {
				for (String pattern1 : patterns1) {
					for (String pattern2 : patterns2) {
						result.add(RouterUtils.combine(pattern1, pattern2));
					}
				}
			} else if (patterns1.length != 0) {
				result = Lists.newArrayList(patterns1);
			} else if (patterns2.length != 0) {
				result = Lists.newArrayList(patterns2);
			} else {
				result.add(StringUtils.EMPTY);
			}

			// method
			RequestMethod requestMethod = classMapping.method() == RequestMethod.ALL ? methodMapping.method()
					: classMapping.method();
			this.apiMethod.setApiRequestPaths(prependLeadingSlash(result));
			this.apiMethod.setApiRequestMethod(requestMethod.name());
		}
	}

	private void parseReturn() {
		try {
			ParsedStat.start();
			ApiClass apiClass = ReturnParser.parse(this.method.getGenericReturnType(), this.method.getReturnType(),
					this.paramVariablesMappers);
			this.apiMethod.setApiReturn(apiClass);
		} finally {
			ParsedStat.finish();
		}
	}

	public static ApiMethod parse(Class<?> type, Method method, Map<TypeVariable<?>, Type> paramVariablesMappers) {
		return new MethodParser(type, method, paramVariablesMappers).parse();
	}
}
